//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.Reflection;
using System.Text;
using OpenLS.Spreadsheet.Expressions;
using OpenLS.Spreadsheet.Parsing;

namespace OpenLS.Spreadsheet.UI.Internals
{
    public class Formula : INotifyPropertyChanged
    {
        private readonly ObservableCollection<FormulaReference> _ranges = new ObservableCollection<FormulaReference>();
        private readonly List<FormulaToken> _tokens = new List<FormulaToken>();
        private readonly Worksheet _worksheet;
        private FormulaReference _activeReference;

        private string _text;
        private WorksheetViewModel _worksheetViewModel;
        [Obsolete]
        private Formula(Worksheet worksheet, string text)
        {
            _worksheet = worksheet;
            Parse(text);
        }
        public Formula(WorksheetViewModel worksheetViewModel, string text)
        {
            
            _worksheet = worksheetViewModel.Worksheet; //\\no cache
            _ranges.CollectionChanged += RangesCollectionChanged;
            _worksheetViewModel = worksheetViewModel;
            Parse(text);
        }

        void RangesCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            this.AcceptsMove();
        }

        internal void updateHotReference(int minIndex, int maxIndex)
        {
            int index = minIndex;
            int i = 0;
            HotLiteral = null;
            foreach (FormulaToken token in Tokens)
            {
                if (i <= index)
                {
                    if (i + token.Text.Length >= index)
                    {
                        if ((i + token.Text.Length > maxIndex))
                        {
                            HotReference = null;
                            ActiveReference = HotReference;
                            break;
                        }
                        if (token is FormulaReference)
                        {
                            // SJE: 11-07 try to inactivate hot formula
                            // HotReference = ((FormulaReference) token);
                            // ActiveReference = HotReference;
                            return;
                        }
                        if (token is LiteralFormulaToken)
                        {
                            LiteralFormulaToken lt = (LiteralFormulaToken) token;
                            if (lt.IsConstant)
                            {
                                HotLiteral = (LiteralFormulaToken) token;
                                return;
                            }
                        }
                    }
                }
                i += token.Text.Length;
            }
            HotReference = null;
            ActiveReference = HotReference;
        }

        


        public Worksheet Worksheet
        {
            get { return _worksheet; }
        }

        public string OriginalText
        {
            get { return _text; }
            private set
            {
                _text = value;
                notifyPropertyChanged("OriginalText");
            }
        }

        public string Text
        {
            get
            {
                var sb = new StringBuilder();
                foreach (FormulaToken token in Tokens)
                    sb.Append(token.Text);
                return sb.ToString();
            }
        }

        public ObservableCollection<FormulaReference> References
        {
            get { return _ranges; }
        }

        internal List<FormulaToken> Tokens
        {
            get { return _tokens; }
        }

        public FormulaReference ActiveReference
        {
            get { return _activeReference; }
            private set
            {
                _activeReference = value;
                notifyPropertyChanged("ActiveReference");
            }
        }

        private FormulaReference _hotReference;
        private LiteralFormulaToken _hotLiteral;
        private LiteralFormulaToken HotLiteral
        {
            get { return _hotLiteral; }
            set
            {
                if (_hotLiteral == value)
                    return;
                _hotLiteral = value;
            }
        }

        public bool AcceptsReference { get { return HotReference != null || HotLiteral != null; } }
        private FormulaReference HotReference
        {
            get { return _hotReference; }
             set
            {
                if (_hotReference == value)
                    return;
                _hotReference = value;
            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal int LastIndexOf(FormulaToken token)
        {
            int result = 0;
            foreach (FormulaToken formulaToken in Tokens)
            {
                result += formulaToken.Text.Length;
                if (formulaToken == token)
                    return result;
            }
            throw new NotSupportedException();
        }

        internal void Parse(string text)
        {
            text = text.Replace("\b", "");
            OriginalText = text;
            if (text.Trim().StartsWith("="))

                ParseFunctionCall(text);
            else
                ParseString(text);
            notifyPropertyChanged("Text");
        }

        private void ParseString(string text)

        {
            clearReferences();
            Tokens.Clear();
            Tokens.Add(new LiteralFormulaToken(new OriginalToken(text)));
        }

        private void ParseFunctionCall(string text)
        {
            var parser = new FormulaParser();
            var oldReferences = new List<FormulaReference>();
            oldReferences.AddRange(References);
            clearReferences();
            parser._lexer.SetInput(text);
            Tokens.Clear();
            List<Token> tokens;
            tokens = new List<Token>(parser.GetTokens());
            if (text.EndsWith(" "))
                tokens.Add(new SpaceToken());
            for (int i = 0; i < tokens.Count - 1; i++)
            {
                Token t = tokens[i];
                Token nextToken = tokens[i + 1];
                if (t is Numeric && nextToken is Identifier)
                {
                    string s = ((Numeric) t).OriginalString;
                    var nextId = (Identifier) nextToken;
                    if (nextId.Value.StartsWith(":"))
                    {
                        string newStr = s + nextId.Value;
                        tokens.RemoveAt(i);
                        tokens.RemoveAt(i);
                        tokens.Insert(i, new Identifier(newStr));
                        i--;
                    }
                }
            }
            string sheetNameCandidate = null;
            string sheetName = null;
            foreach (Token token in getRegroupedTokens(tokens))
            {
                FormulaToken tokenToAdd = new LiteralFormulaToken(token);
                if (token is Exclamation)
                {
                    sheetName = sheetNameCandidate;
                    sheetNameCandidate = null;
                }
                if (token is Identifier)
                {
                    Identifier id = (Identifier) token;
                    sheetNameCandidate = id.Value;
                    FormulaReference r = null;
                    CellReference cr = Worksheet.ParseCellReference(((Identifier) token).Value);
                    if (cr != null)
                    {
                        if (sheetName != null)
                        {
                            Spreadsheet.Worksheet targetWorksheet = Worksheet.Workbook.GetWorksheet(sheetName);
                            if (targetWorksheet != null)
                            {
                                Tokens.RemoveAt(Tokens.Count - 1);
                                Tokens.RemoveAt(Tokens.Count - 1);
                                cr = cr.Projected(targetWorksheet);
                            }
                        }
                        r = new FormulaCellsReference(token, cr, this);
                    }
                    else
                    {
                        RangeReference rr = Worksheet.ParseRangeReference(((Identifier)token).Value);

                        if (rr != null)
                        {
                            if (sheetName != null)
                            {
                                Spreadsheet.Worksheet targetWorksheet = Worksheet.Workbook.GetWorksheet(sheetName);
                                if (targetWorksheet != null)
                                {
                                    Tokens.RemoveAt(Tokens.Count - 1);
                                    Tokens.RemoveAt(Tokens.Count - 1);
                                    rr = rr.Projected(targetWorksheet);
                                }
                            }
                            if (rr is CellsRangeReference)
                                r = new FormulaCellsReference(token, (CellsRangeReference)rr, this);
                            else if (rr is FullRangeReference<Column, Row>)
                                r = new FormulaColumnsReference(token, (FullRangeReference<Column, Row>)rr, this);
                            else if (rr is FullRangeReference<Row, Column>)
                                r = new FormulaRowsReference(token, (FullRangeReference<Row, Column>)rr, this);
                        }
                    }
                    if (r != null)
                    {
                        foreach (FormulaReference oldR in oldReferences)
                        {
                            if (oldR.Equals(r))
                            {
                                r = oldR;
                                oldReferences.Remove(oldR);
                                break;
                            }
                        }
                        tokenToAdd = r;
                        addReferenceInternal(r);
                        sheetNameCandidate = null;
                    }
                    sheetName = null;
                }
                else if (token is SingleQuoteIdentifier)
                {
                    sheetNameCandidate = ((SingleQuoteIdentifier) token).Value;
                    sheetName = null;
                }
                else
                {
                    sheetNameCandidate = null;
                }
                Tokens.Add(tokenToAdd);
            }
            if (ReferencesChanged != null)
                ReferencesChanged(this, new EventArgs());
            ActiveReference = null;
            if (References.Count > 0)
                ActiveReference = References[References.Count - 1];
        }

        private static IEnumerable<Token> getRegroupedTokens(IEnumerable<Token> tokens)
        {
            Token lastToken = null;
            foreach (Token token in tokens)
            {
                if (token is Identifier || token is Dollar || token is Colon || token is Numeric)
                {
                    if (lastToken == null)
                    {
                        lastToken = new Identifier(token.ToString());
                    }
                    else if (lastToken is Identifier)
                    {
                        lastToken = new Identifier(lastToken + token.ToString());
                    }
                    else
                    {
                        yield return lastToken;
                        lastToken = token; //\\  new Identifier(token.ToString());
                    }
                }
                else
                {
                    if (lastToken != null)
                        yield return lastToken;
                    lastToken = token;
                }
            }
            if (lastToken != null)
                yield return lastToken;
        }

        private void addReferenceInternal(FormulaReference r)
        {
            References.Add(r);
            r.PropertyChanged += rPropertyChanged;
        }

        private void clearReferences()
        {
            foreach (FormulaReference fr in References)
                fr.PropertyChanged -= rPropertyChanged;
            References.Clear();
        }

        private void rPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Label" && ReferenceLabelChanged != null)
                ReferenceLabelChanged(sender, e);
            if (e.PropertyName == "Label")
                notifyPropertyChanged("Text");
        }

        internal event EventHandler ReferencesChanged;
        internal event PropertyChangedEventHandler ReferenceLabelChanged;


        internal bool AcceptsMove()
        {
            if (Tokens.Count > 0)
            {
                var literalFormulaToken = Tokens[0] as LiteralFormulaToken;
                if (literalFormulaToken != null)
                {
                    Token t = literalFormulaToken.Token;
                    if (t is Symbol)
                    {
                        var symbol = (Symbol) t;
                        if (symbol.Type == TokenId.Equality)
                            return true;
                    }
                }
            }
            return false;
        }

        internal bool AcceptsPoint()
        {
            if (Tokens.Count == 0)
                return false;
            var literalFormulaToken = Tokens[0] as LiteralFormulaToken;
            if (literalFormulaToken == null)
                return false;
            Token t = literalFormulaToken.Token;
            if (!(t is Symbol))
                return false;
            var symbol = (Symbol) t;
            if (symbol.Type != TokenId.Equality)
                return false;
            FormulaToken lastToken = Tokens[Tokens.Count - 1];
            if (lastToken.Token.Type == TokenId.RightParenthesis)
                return false;
            return true;
        }

        private void notifyPropertyChanged(string propertyName)
        {
            Contract.Requires(GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null);
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }

        internal void AddReference(FormulaReference fr)
        {
            Parse(Text + fr.FullLabel);
        }

        internal void ActivateCellReference(Cell cell)
        {
            CellReference cr = new DynamicCellReference(new AnchoredRowReference(cell.Row, false),
                                                        new AnchoredColumnReference(cell.Column, false));
            string ss = "";
            if (cr.Worksheet != this.Worksheet)
                ss = cr.Worksheet.SafeName + "!";
            ss = ss + cr.NameOrlabel;

            var id = new Identifier(ss) {Position = OriginalText.Length};
            id.Length = id.Value.Length;
            var fr = new FormulaCellsReference(id, cr, this);
            var r = ActiveReference as FormulaCellsReference;
            if (r == null)
            {
                if (HotLiteral != null)
                {
                    int index = Tokens.IndexOf(HotLiteral);
                    this.Tokens.Remove(HotLiteral);
                    Tokens.Insert(index, new FormulaCellsReference(new Identifier(cr.Label), cr, this));
                    notifyPropertyChanged("Text");
                    return;
                }
                else
                    r = HotReference as FormulaCellsReference;
            }
            if (r == null)
            {
                int refCount = References.Count;
                AddReference(fr);
                if (References.Count > refCount)
                {
                    ActiveReference = References[References.Count - 1];
                }
            }
            else
                r.SetCellReference(cr);
        }

        internal void ActivateColumnReference(Column column)
        {
            ColumnReference cr = new AnchoredColumnReference(column, false);
            var id = new Identifier(cr.Label) { Position = OriginalText.Length };
            id.Length = id.Value.Length;
            var fr = new FormulaColumnsReference(id, cr, this);
            var r = ActiveReference as FormulaColumnsReference;
            if (r == null)
            {
                AddReference(fr);
                ActiveReference = References[References.Count - 1];
            }
            else
                r.SetDimensionReference(cr);
        }

        internal void FireRefresh(FormulaToken lastToken, LiteralFormulaToken newToken)
        {
            if (TokenChanged != null)
                TokenChanged(this, new Pair<FormulaToken> {Old = lastToken, New = newToken});
        }

        internal event EventHandler<Pair<FormulaToken>> TokenChanged;

        internal void ActivateReference(Cell targetCell)
        {
            ActivateCellReference(targetCell);
        }

        internal void ActivateRowReference(Row row)
        {
            RowReference cr = new AnchoredRowReference(row, false);
            var id = new Identifier(cr.Label) { Position = OriginalText.Length };
            id.Length = id.Value.Length;
            var fr = new FormulaRowsReference(id, cr, this);
            var r = ActiveReference as FormulaRowsReference;
            if (r == null)
            {
                AddReference(fr);
                ActiveReference = References[References.Count - 1];
            }
            else
                r.SetDimensionReference(cr);
        }

        #region Nested type: Pair

        internal class Pair<T> : EventArgs
        {
            public T Old { get; set; }
            public T New { get; set; }
        }

        #endregion
    }
}